package com.example.srb.core.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.example.srb.common.exception.BusinessException;
import com.example.srb.core.enums.LendStatusEnum;
import com.example.srb.core.enums.ReturnMethodEnum;
import com.example.srb.core.enums.TransTypeEnum;
import com.example.srb.core.hfb.HfbConst;
import com.example.srb.core.hfb.RequestHelper;
import com.example.srb.core.mapper.BorrowerMapper;
import com.example.srb.core.mapper.UserAccountMapper;
import com.example.srb.core.mapper.UserInfoMapper;
import com.example.srb.core.pojo.bo.TransFlowBO;
import com.example.srb.core.pojo.entity.*;
import com.example.srb.core.mapper.LendMapper;
import com.example.srb.core.pojo.vo.BorrowInfoApprovalVO;
import com.example.srb.core.pojo.vo.BorrowerDetailVO;
import com.example.srb.core.service.*;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.srb.core.util.*;
import io.swagger.models.auth.In;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.swing.text.DateFormatter;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * <p>
 * 标的准备表 服务实现类
 * </p>
 *
 * @author Helen
 * @since 2021-02-20
 */
@Service
@Slf4j
public class LendServiceImpl extends ServiceImpl<LendMapper, Lend> implements LendService {

    @Resource
    private DictService dictService;
    @Resource
    private BorrowerMapper borrowerMapper;
    @Resource
    private BorrowerService borrowerService;
    @Resource
    private UserInfoMapper userInfoMapper;
    @Resource
    private UserAccountMapper userAccountMapper;
    @Resource
    private TransFlowService transFlowService;
    @Resource
    private LendItemService lendItemService;
    @Resource
    private LendReturnService lendReturnService;
    @Resource
    private LendItemReturnService lendItemReturnService;

    @Override
    public void createLend(BorrowInfoApprovalVO borrowInfoApprovalVO, BorrowInfo borrowInfo) {
        Lend lend = new Lend();
        lend.setUserId(borrowInfo.getUserId());
        lend.setBorrowInfoId(borrowInfo.getId());
        lend.setLendNo(LendNoUtils.getLendNo());//生成借款流水号
        lend.setTitle(borrowInfoApprovalVO.getTitle());
        lend.setAmount(borrowInfo.getAmount());
        lend.setPeriod(borrowInfo.getPeriod());//投资期数
        lend.setLendYearRate(borrowInfoApprovalVO.getLendYearRate().divide(new BigDecimal(100)));//从审批对象中获取
        lend.setServiceRate(borrowInfoApprovalVO.getServiceRate().divide(new BigDecimal(100)));//从审批对象中获取
        lend.setReturnMethod(borrowInfo.getReturnMethod());
        lend.setLowestAmount(new BigDecimal(100));//最低投资金额
        lend.setInvestAmount(new BigDecimal(0));//已投金额
        lend.setInvestNum(0);//投资人数
        lend.setPublishDate(LocalDateTime.now());

        //起息日期
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        LocalDate lendStartDate = LocalDate.parse(borrowInfoApprovalVO.getLendStartDate(),formatter);
        lend.setLendStartDate(lendStartDate);

        //结束日期
        LocalDate lendEndDate = lendStartDate.plusMonths(borrowInfo.getPeriod());
        lend.setLendEndDate(lendEndDate);

        //标的描述
        lend.setLendInfo(borrowInfoApprovalVO.getLendInfo());

        // 平台预期收益 = 标的金额 * (年化 / 12 * 期数)
        BigDecimal monthRate = lend.getServiceRate().divide(new BigDecimal(12),8,BigDecimal.ROUND_DOWN);//除以12个月，保留8位小数，向下保留
        BigDecimal expectAmount = lend.getAmount().multiply(monthRate).multiply(new BigDecimal(lend.getPeriod()));
        lend.setExpectAmount(expectAmount);

        //实际收益
        lend.setRealAmount(new BigDecimal(0));

        //标的状态
        lend.setStatus(LendStatusEnum.INVEST_RUN.getStatus());//募资中

        //审核时间
        lend.setCheckTime(LocalDateTime.now());

        //审核人
        lend.setCheckAdminId(1L);

        //插入数据库
        baseMapper.insert(lend);
    }

    @Override
    public List<Lend> selectList() {
        List<Lend> lendList = baseMapper.selectList(null);
        lendList.forEach(lend -> {
            String returnMethod = dictService.getNameByParentDictCodeAndValue("returnMethod", lend.getReturnMethod());
            String status = LendStatusEnum.getMsgByStatus(lend.getStatus());
            lend.getParam().put("returnMethod",returnMethod);
            lend.getParam().put("status",status);
        });
        return lendList;
    }

    @Override
    public Map<String, Object> getLendDetail(Long id) {
        //查询标的对象
        Lend lend = baseMapper.selectById(id);

        //组装数据
        String returnMethod = dictService.getNameByParentDictCodeAndValue("returnMethod", lend.getReturnMethod());
        String status = dictService.getNameByParentDictCodeAndValue("status", lend.getStatus());
        lend.getParam().put("returnMethod",returnMethod);
        lend.getParam().put("status",status);

        //根据user_id获取借款人对象
        QueryWrapper<Borrower> wrapper = new QueryWrapper<>();
        wrapper.eq("user_id",lend.getUserId());
        Borrower borrower = borrowerMapper.selectOne(wrapper);

        //组装借款人对象
        BorrowerDetailVO detailVO = borrowerService.getBorrowerDetailVOById(borrower.getId());

        //组装数据
        Map<String,Object> result = new HashMap<>();
        result.put("lend",lend);
        result.put("borrower",detailVO);

        return result;
    }

    @Override
    public BigDecimal getInterestCount(BigDecimal invest, BigDecimal yearRate, Integer totalmonth, Integer returnMethod) {

        BigDecimal interestCount;
        //计算总利息
        if (returnMethod.intValue() == ReturnMethodEnum.ONE.getMethod()){
            interestCount = Amount1Helper.getInterestCount(invest,yearRate,totalmonth);
        }else if (returnMethod.intValue() == ReturnMethodEnum.TWO.getMethod()){
            interestCount = Amount2Helper.getInterestCount(invest,yearRate,totalmonth);
        }else if (returnMethod.intValue() == ReturnMethodEnum.THREE.getMethod()){
            interestCount = Amount3Helper.getInterestCount(invest,yearRate,totalmonth);
        }else{
            interestCount = Amount4Helper.getInterestCount(invest,yearRate,totalmonth);
        }
        return interestCount;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void makeLoan(Long id) {
        //获取标的信息
        Lend lend = baseMapper.selectById(id);

        //放款接口调用
        Map<String,Object> paramMap = new HashMap<>();
        paramMap.put("agentId", HfbConst.AGENT_ID);
        paramMap.put("agentProjectCode",lend.getLendNo());//放款项目编号
        String loanNo = LendNoUtils.getLoanNo();
        paramMap.put("agentBillNo",loanNo);//放款单号

        //平台收益，放款扣除，借款人借款实际金额=借款金额-平台收益
        //月年化
        //divide是做除法运算，第一个参数是除数，第二个参数代表保留几位小数，第三个代表使用的模式（直接省略多余的小数，比如1.28如果保留一位小数，得到的就是1.2）
        BigDecimal monthRate = lend.getServiceRate().divide(new BigDecimal(12), 8, BigDecimal.ROUND_DOWN);
        //平台实际收益 = 已投金额 * 月年化 * 标的期数
        BigDecimal realAmount = lend.getInvestAmount().multiply(monthRate).multiply(new BigDecimal(lend.getPeriod()));
        paramMap.put("mchFee",realAmount);//商户手续费(平台实际收益)

        paramMap.put("note","放款啦");//放款备注
        paramMap.put("timestamp", RequestHelper.getTimestamp());//时间戳
        paramMap.put("sign",RequestHelper.getSign(paramMap));//验签参数

        //发送同步远程调用
        //log.info("放款参数" + JSON.toJSONString(paramMap));
        JSONObject result = RequestHelper.sendRequest(paramMap,HfbConst.MAKE_LOAD_URL);
        log.info("放款结果" + JSON.toJSONString(result));//     result.toJSONString()转换成json字符串的另一种写法

        //放款失败
        if (!"0000".equals(result.getString("resultCode"))){
            throw new BusinessException(result.getString("resultMsg"));
        }

        //放款成功
        //(1)更新标的相关信息
        lend.setStatus(LendStatusEnum.PAY_RUN.getStatus());//还款中
        lend.setPaymentTime(LocalDateTime.now());
        lend.setRealAmount(realAmount);//实际收益
        baseMapper.updateById(lend);

        //(2)給借款账号转入金额
        Long userId = lend.getUserId();
        UserInfo userInfo = userInfoMapper.selectById(userId);
        BigDecimal voteAmt = new BigDecimal(result.getString("voteAmt"));//放款金额
        String bindCode = userInfo.getBindCode();//借款人的绑定编号
        userAccountMapper.updateAccount(bindCode,voteAmt,new BigDecimal(0));

        //（3）增加借款人交易流水
        TransFlowBO transFlowBO = new TransFlowBO();
        transFlowBO.setAgentBillNo(result.getString("agentBillNo"));
        transFlowBO.setBindCode(bindCode);
        transFlowBO.setAmount(voteAmt);
        transFlowBO.setTransTypeEnum(TransTypeEnum.BORROW_BACK);//放款到账
        transFlowBO.setMemo("借款放款到账，编号：" + lend.getLendNo() + "   项目名称:" +lend.getTitle());
        transFlowService.saveTransFlow(transFlowBO);

        //（4）解冻并扣除投资人资金
        // 获取标的下的投资列表
        List<LendItem> lendItemList = lendItemService.selectByLendId(id,1);
        lendItemList.stream().forEach(lendItem -> {
            //获取投资人信息
            Long investUserId = lendItem.getInvestUserId();
            UserInfo investUserInfo = userInfoMapper.selectById(investUserId);

            //投资人账号冻结金额转出
            BigDecimal investAmount = lendItem.getInvestAmount();
            String investBindCode = investUserInfo.getBindCode();
            userAccountMapper.updateAccount(investBindCode,new BigDecimal(0), investAmount.negate());

            //新增投资人交易流水
            TransFlowBO investTransFlowBO = new TransFlowBO();
            investTransFlowBO.setAgentBillNo(LendNoUtils.getTransNo());
            investTransFlowBO.setBindCode(investBindCode);
            investTransFlowBO.setAmount(investAmount);
            investTransFlowBO.setTransTypeEnum(TransTypeEnum.INVEST_UNLOCK);//放款解锁
            investTransFlowBO.setMemo("冻结资金转出，开始放款，编号" + lend.getLendNo() +  "  项目名称: " + lend.getTitle());
            transFlowService.saveTransFlow(investTransFlowBO);
        });



        //放款成功生成借款人还款计划和投资人回款计划
        // TODO
        this.repaymentPlan(lend);
    }


    //还款计划
    private void repaymentPlan(Lend lend){
        //还款计划列表
        List<LendReturn> lendReturnList = new ArrayList<>();

        //按还款期数生成还款计划
        int returnTime = lend.getPeriod().intValue();
        for (int i = 1; i<= returnTime; i++){
            //创建还款计划对象
            LendReturn lendReturn = new LendReturn();
            lendReturn.setLendId(lend.getId());
            lendReturn.setReturnNo(LendNoUtils.getReturnNo());
            lendReturn.setBorrowInfoId(lend.getBorrowInfoId());
            lendReturn.setUserId(lend.getUserId());
            lendReturn.setAmount(lend.getAmount());
            lendReturn.setBaseAmount(lend.getInvestAmount());//计息本金额 ----->已投金额
            lendReturn.setLendYearRate(lend.getLendYearRate());
            lendReturn.setCurrentPeriod(i);//当前期数
            lendReturn.setReturnMethod(lend.getReturnMethod());
            //说明：还款计划中的这三项 = 回款计划中对应的这三项和：因此需要先生成对应的回款计划
            //			lendReturn.setPrincipal();
            //			lendReturn.setInterest();
            //			lendReturn.setTotal();
            lendReturn.setFee(new BigDecimal(0));//手续费
            lendReturn.setReturnDate(lend.getLendStartDate().plusMonths(i));//指定还款日期  出借日期加一个月开始还款
            lendReturn.setOverdue(false);//是否逾期
            if (i == returnTime){//最后一个月
                //标识为最后一次还款
                lendReturn.setLast(true);
            }else {
                lendReturn.setLast(false);
            }
            lendReturn.setStatus(0);//状态（0-未归还 1-已归还）
            lendReturnList.add(lendReturn);
        }

        //批量保存
        lendReturnService.saveBatch(lendReturnList);

        ///生成期数和还款记录的id对应的键值对集合
        Map<Integer, Long> lendReturnMap = lendReturnList.stream().collect(
                Collectors.toMap(LendReturn::getCurrentPeriod, LendReturn::getId)
        );

        //======================================================
        //=============获取所有投资者，生成回款计划===================
        //======================================================
        //创建所有投资的所有回款记录列表
        List<LendItemReturn> lendItemReturnAllList = new ArrayList<>();

        //获取当前标的下的所有的已支付的投资记录
        List<LendItem> lendItemList = lendItemService.selectByLendId(lend.getId(),1);
        for (LendItem lendItem: lendItemList) {
            //创建回款计划列表
            List<LendItemReturn> lendItemReturnList = this.returnInvest(lendItem.getId(), lendReturnMap, lend);
            lendItemReturnAllList.addAll(lendItemReturnList);
        }

        //更新还款计划中的相关金额数据
        for (LendReturn lendReturn : lendReturnList){
            //通过filter、map、reduce将相关期数的回款数据过滤出来
            //将当前期数的所有投资人的数据相加，就是当前期数的所有投资人的回款数据（本金、利息、总金额）
            BigDecimal sumPrincipal = lendItemReturnAllList.stream()
                    .filter(item -> item.getLendReturnId().longValue() == lendReturn.getId().longValue())
                    .map(LendItemReturn::getPrincipal)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);

            BigDecimal sumInterest = lendItemReturnAllList.stream()
                    .filter(item -> item.getLendReturnId().longValue() == lendReturn.getId().longValue())
                    .map(LendItemReturn::getInterest)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);

            BigDecimal sumTotal = lendItemReturnAllList.stream()
                    .filter(item -> item.getLendReturnId().longValue() == lendReturn.getId().longValue())
                    .map(LendItemReturn::getTotal)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);

            //将计算出的数据填充入还款计划记录：设置本金、利息、总金额
            lendReturn.setPrincipal(sumPrincipal);//每期还款本金
            lendReturn.setInterest(sumInterest);//每期还款利息
            lendReturn.setTotal(sumTotal);//每期还款本息
        }

        //批量更新还款计划列表
        lendReturnService.updateBatchById(lendReturnList);
    }

    /**
     * 回款计划
     *
     * @param lendItemId
     * @param lendReturnMap 还款期数与还款计划id对应map
     * @param lend
     * @return
     */
    public List<LendItemReturn> returnInvest(Long lendItemId,Map<Integer,Long> lendReturnMap,Lend lend ){
        //投资记录
        LendItem lendItem = lendItemService.getById(lendItemId);

        //投资金额
        BigDecimal investAmount = lendItem.getInvestAmount();
        //年化利率
        BigDecimal lendYearRate = lendItem.getLendYearRate();
        //投资期数
        Integer period = lend.getPeriod();

        Map<Integer,BigDecimal> mapInterest =null;//还款期数 -> 利息
        Map<Integer,BigDecimal> mapPrincipal = null;//还款期数 -> 本金

        //根据还款方式计算本金和利息
        if (lend.getReturnMethod().intValue() == ReturnMethodEnum.ONE.getMethod()){
            mapInterest = Amount1Helper.getPerMonthInterest(investAmount,lendYearRate,period);//利息
            mapPrincipal = Amount1Helper.getPerMonthPrincipal(investAmount,lendYearRate,period);//本金
        }else if (lend.getReturnMethod().intValue() == ReturnMethodEnum.TWO.getMethod()){
            mapInterest = Amount2Helper.getPerMonthInterest(investAmount,lendYearRate,period);
            mapPrincipal = Amount2Helper.getPerMonthPrincipal(investAmount,lendYearRate,period);
        }else if (lend.getReturnMethod().intValue() == ReturnMethodEnum.THREE.getMethod()){
            mapInterest = Amount3Helper.getPerMonthInterest(investAmount,lendYearRate,period);
            mapPrincipal = Amount3Helper.getPerMonthPrincipal(investAmount,lendYearRate,period);
        }else {
            mapInterest = Amount4Helper.getPerMonthInterest(investAmount,lendYearRate,period);
            mapPrincipal = Amount4Helper.getPerMonthPrincipal(investAmount,lendYearRate,period);
        }

        //创建回款计划列表
        List<LendItemReturn> lendItemReturnList = new ArrayList<>();
        for (Map.Entry<Integer, BigDecimal> entry : mapInterest.entrySet()) {
            Integer currentPeriod = entry.getKey();
            //根据当前期数，获取还款计划的id
            Long lendReturnId = lendReturnMap.get(currentPeriod);

            //创建回款计划记录
            LendItemReturn lendItemReturn = new LendItemReturn();
            //将还款记录关联到回款记录
            lendItemReturn.setLendReturnId(lendReturnId);
            //设置回款记录的基本属性
            lendItemReturn.setLendItemId(lendItemId);
            lendItemReturn.setInvestUserId(lendItem.getInvestUserId());
            lendItemReturn.setLendId(lendItem.getLendId());
            lendItemReturn.setInvestAmount(lendItem.getInvestAmount());
            lendItemReturn.setLendYearRate(lend.getLendYearRate());
            lendItemReturn.setCurrentPeriod(currentPeriod);
            lendItemReturn.setReturnMethod(lend.getReturnMethod());
            //计算回款本金、利息和总额（注意最后一个月的计算）
            if (currentPeriod.intValue() == lend.getPeriod().intValue()){//最后一期
                //前几次回款的本金之和
                BigDecimal sumPrincipal = lendItemReturnList.stream()
                        .map(LendItemReturn::getPrincipal)
                        .reduce(BigDecimal.ZERO,BigDecimal::add);//第一个参数是初值，第二个参数是累加器   reduce返回计算后的结果

                //最后一期应还本金 = 用当前投资人的总投资金额 - 除了除了最后一期前面期数计算出来的所有的应还本金
                BigDecimal lastPrincipal = lendItem.getInvestAmount().subtract(sumPrincipal);//subtract(要减去的值)
                lendItemReturn.setPrincipal(lastPrincipal);

                //利息
                BigDecimal sumInterest = lendItemReturnList.stream()
                        .map(LendItemReturn::getInterest)
                        .reduce(BigDecimal.ZERO, BigDecimal::add);
                BigDecimal lastInterest = lendItem.getExpectAmount().subtract(sumInterest);
                lendItemReturn.setInterest(lastInterest);
            }else {//非最后一期
                //利息
                lendItemReturn.setInterest(mapInterest.get(currentPeriod));
                //本金
                lendItemReturn.setPrincipal(mapPrincipal.get(currentPeriod));
            }

            //回款总金额
            lendItemReturn.setTotal(lendItemReturn.getPrincipal().add(lendItemReturn.getInterest()));

            //设置回款状态和是否逾期等其他属性
            lendItemReturn.setFee(new BigDecimal("0"));
            lendItemReturn.setReturnDate(lend.getLendStartDate().plusMonths(currentPeriod));
            lendItemReturn.setOverdue(false);//是否逾期
            lendItemReturn.setStatus(0);//状态（0-未归还 1-已归还）

            //将回款记录放入回款列表
            lendItemReturnList.add(lendItemReturn);
        }

        //批量保存
        lendItemReturnService.saveBatch(lendItemReturnList);
        return lendItemReturnList;
    }
}
